package com.hw.common.framework.spring.error;

import com.hw.common.bean.ResponseBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;


/*****
 * <pre>
 * 类名称：DefaultHandlerMethodExceptionResolver
 * 类描述：方法异常自定义缺省处理器
 * 创建人：JokenWang
 * 创建时间：2015年9月7日 下午8:56:21
 * </pre>
 * 
 * @version 1.0.0
 */
public class DefaultHandlerMethodExceptionResolver extends ExceptionHandlerExceptionResolver
{
    
    private static final Logger LOGGER = LoggerFactory.getLogger("defaultHandlerMethodExceptionResolver");
    
    private String defaultErrorView;
    
    private Properties exceptionMappings;
    
    public Properties getExceptionMappings() {
		return exceptionMappings;
	}

	public void setExceptionMappings(Properties exceptionMappings) {
		this.exceptionMappings = exceptionMappings;
	}

	public String getDefaultErrorView()
    {
        return defaultErrorView;
    }
    
    public void setDefaultErrorView(String defaultErrorView)
    {
        this.defaultErrorView = defaultErrorView;
    }
    
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response,
            HandlerMethod handlerMethod, Exception exception)
    {
        
        if (handlerMethod == null)
        {
            return null;
        }
        
        Method method = handlerMethod.getMethod();
        if (method == null)
        {
            return null;
        }
        
        Class<?> returnType = method.getReturnType();// 方法返回值类型
        ModelAndView returnValue = super.doResolveHandlerMethodException(request, response, handlerMethod, exception);
        ResponseBody responseBodyAnn = AnnotationUtils.findAnnotation(method, ResponseBody.class);
        if (responseBodyAnn != null || (returnType != ModelAndView.class && returnValue != null))
        {
            try
            {
                ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(method, ResponseStatus.class);
                if (responseStatusAnn != null)
                {
                    HttpStatus responseStatus = responseStatusAnn.value();
                    String reason = responseStatusAnn.reason();
                    if (!StringUtils.hasText(reason))
                    {
                        response.setStatus(responseStatus.value());
                    }
                    else
                    {
                        try
                        {
                            response.sendError(responseStatus.value(), reason);
                        }
                        catch (IOException e)
                        {
                            LOGGER.error("{}", e);
                        }
                    }
                }
                
                if (returnValue == null)
                {
                    ResponseBean responseBean = new ResponseBean();
                    responseBean.addError(exception.getMessage());
                    handleResponseError(responseBean, request, response);
                    
                    return new ModelAndView();
                }
                
                return handleResponseBody(returnValue, request, response);
            }
            catch (Exception e)
            {
                LOGGER.error("{}", e);
                return null;
            }
        }
        
        if (null == returnValue)
        {
            returnValue = new ModelAndView();
            if (null == returnValue.getViewName())
            {
            	String viewName = determineViewName(exception,request);
                returnValue.addObject("message", exception.getMessage());
                if(StringUtils.isEmpty(viewName)){
                	viewName = defaultErrorView;
                }
                returnValue.setViewName(viewName);
            }
        }
        
        return returnValue;
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private ModelAndView handleResponseBody(ModelAndView returnValue, HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException
    {
        Map<String, ?> value = returnValue.getModelMap();
        HttpInputMessage inputMessage = new ServletServerHttpRequest(request);
        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
        if (acceptedMediaTypes.isEmpty())
        {
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
        }
        MediaType.sortByQualityValue(acceptedMediaTypes);
        
        ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
        Class<?> returnValueType = value.getClass();
        List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
        if (messageConverters != null)
        {
            for (MediaType acceptedMediaType : acceptedMediaTypes)
            {
                for (HttpMessageConverter messageConverter : messageConverters)
                {
                    if (messageConverter.canWrite(returnValueType, acceptedMediaType))
                    {
                        messageConverter.write(value, acceptedMediaType, outputMessage);
                        
                        return new ModelAndView();
                    }
                }
            }
        }
        
        if (logger.isWarnEnabled())
        {
            logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and "
                    + acceptedMediaTypes);
        }
        
        outputMessage.close();
        
        return null;
    }
    
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private ModelAndView handleResponseError(ResponseBean returnValue, HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException
    {
        HttpInputMessage inputMessage = new ServletServerHttpRequest(request);
        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
        if (acceptedMediaTypes.isEmpty())
        {
            acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
        }
        MediaType.sortByQualityValue(acceptedMediaTypes);
        
        ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
        Class<?> returnValueType = returnValue.getClass();
        List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
        if (messageConverters != null)
        {
            for (MediaType acceptedMediaType : acceptedMediaTypes)
            {
                for (HttpMessageConverter messageConverter : messageConverters)
                {
                    if (messageConverter.canWrite(returnValueType, acceptedMediaType))
                    {
                        messageConverter.write(returnValue, acceptedMediaType, outputMessage);
                        
                        return new ModelAndView();
                    }
                }
            }
        }
        
        if (logger.isWarnEnabled())
        {
            logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and "
                    + acceptedMediaTypes);
        }
        
        outputMessage.close();
        
        return null;
    }
    
    protected String determineViewName(Exception ex, HttpServletRequest request) {
		String viewName = null;
		// Check for specific exception mappings.
		if (this.exceptionMappings != null) {
			viewName = findMatchingViewName(this.exceptionMappings, ex);
		}
		// Return default error view else, if defined.
		if (viewName == null && this.defaultErrorView != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type [" +
						ex.getClass().getName() + "]");
			}
			viewName = this.defaultErrorView;
		}
		return viewName;
	}

	/**
	 * Find a matching view name in the given exception mappings.
	 * @param exceptionMappings mappings between exception class names and error view names
	 * @param ex the exception that got thrown during handler execution
	 * @return the view name, or {@code null} if none found
	 * @see #setExceptionMappings
	 */
	protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
		String viewName = null;
		String dominantMapping = null;
		int deepest = Integer.MAX_VALUE;
		for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
			String exceptionMapping = (String) names.nextElement();
			int depth = getDepth(exceptionMapping, ex);
			if (depth >= 0 && (depth < deepest || (depth == deepest &&
					dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) {
				deepest = depth;
				dominantMapping = exceptionMapping;
				viewName = exceptionMappings.getProperty(exceptionMapping);
			}
		}
		if (viewName != null && logger.isDebugEnabled()) {
			logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() +
					"], based on exception mapping [" + dominantMapping + "]");
		}
		return viewName;
	}
	
	/**
	 * Return the depth to the superclass matching.
	 * <p>0 means ex matches exactly. Returns -1 if there's no match.
	 * Otherwise, returns depth. Lowest depth wins.
	 */
	protected int getDepth(String exceptionMapping, Exception ex) {
		return getDepth(exceptionMapping, ex.getClass(), 0);
	}

	private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
		if (exceptionClass.getName().contains(exceptionMapping)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
		if (exceptionClass.equals(Throwable.class)) {
			return -1;
		}
		return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
	}
}
